package fri.pipt.agent.deathrow;

import java.util.ArrayList;
import java.util.List;

import fri.pipt.protocol.Neighborhood;

/**
 * 
 * @author dejvid
 * 
 * World je v bistvu svet, ki ga je izkusil agent(in tudi drugi)
 * njegovo koodrdinatno izhodišče je postavljeno v točki odkoder je agent
 * začel. Če kateri drugi agent sporoča sveti o opaženih lokacijah morajo biti
 * tiste lokacije poravnane glede na pozicijo headquartes, da si si lahko sploh koordinatni
 * sistemi medseboj kompatibilni potrebujejo znano SKUPNO referenčno točko!
 * 
 *
 * Map je 2D int tabela 
 *	  
 * int je sestavljen:
 * 0 - 7: nasi agenti
 * 8 - 19: agenti drugih ekip
 * 20: nasa baza
 * 21: nasa zastava
 * 22: nas agent
 * 23: prazno mesto
 *	  
 *	  negativna stevila: zid, baza druge ekipe, zastava druge ekipe
 *	  
 *	  vrednost 0: še neraziskano polje
 *
 */
public class World
{	
	public static final int TILE_OUTOFSCOPE= 1<<31; //Izven mape
	public static final int TILE_EMPTY = 1 << 23; //Prazna
	public static final int TILE_AGENT_ME = 1 << 22;
	public static final int TILE_FLAG = 1 << 21;
	public static final int TILE_HEADQUARTERS = 1 << 20;
	public static final int TILE_WALL = -1;
	public static final int TILE_NOT_VISITED = 0;
	
	private final static int MAX_WIDTH = 500;
	private final static int MAX_HEIGHT = MAX_WIDTH;
		
	public static final Pos2 MOVE_UP = new Pos2(0, -1); 
	public static final Pos2 MOVE_DOWN = new Pos2(0, 1);
	public static final Pos2 MOVE_LEFT = new Pos2(-1, 0);
	public static final Pos2 MOVE_RIGHT = new Pos2(1, 0);
	
	public static final Pos2[] MOVES = { MOVE_UP, MOVE_LEFT, MOVE_DOWN, MOVE_RIGHT};
	
	public enum eDiscoverEvent
	{
		headquartes,
		flag,
		agent
	};
	
	int width;  //current width
	int height; //current height
	
	//T(centerX,centerY) je izhodišče koordinatnega sistema! 1. update reče da je neighborhood okoli te točke!
	int centerX; //center mape po X(width se razteza po x dimenziji)
	int centerY; //center mape po Y(height se razteza po y dimenziji)
	
	int Map[][] = new int[MAX_WIDTH][MAX_HEIGHT];
	ObservableArea observableArea;
	Pos2 headquartersPos = null;
	Pos2 flagPos = null;
	List<SpotedAgent> lstSpotedAgents;
	
	public World()
	{
		centerX = MAX_WIDTH / 2;
		centerY = MAX_HEIGHT / 2;
		observableArea = new ObservableArea();
		lstSpotedAgents = new ArrayList<SpotedAgent>();
	}
	
	/**
	 * 
	 * @param pos - trenutna pozicija agenta
	 * @param neighborhood - okolica agenta!
	 * To ne dela dobro! Zakaj!
	 */
	public void update(Pos2 pos, Neighborhood neighborhood)
	{
		
		int size = neighborhood.getSize();
		for(int x = -size; x <= size; x++)
			for(int y = -size; y <= size; y++)
			{
				//calculate what tile is!
				int nTile = neighborhood.getCell(x, y);
				if(nTile == -100000)
					System.err.println("errr");
				//Izračunamo logično koordinato, glede na pozicijo agenta, ker agent je na sredini heighborhood-a
				int logicX = pos.x + x;
				int logicY = pos.y + y;
				
				//Update observable area
				observableArea.pointSpoted(logicX, logicY);
				
				//TODO: calc tile integer!
				int tile = 0;
				
				if (nTile == Neighborhood.EMPTY) 
				{
					tile = TILE_EMPTY; 
				} else if(nTile == Neighborhood.FLAG)
				{
					//TODO: Flag found!
					tile = TILE_FLAG;
					flagPos = new Pos2(logicX, logicY);
				} else if(nTile == Neighborhood.HEADQUARTERS)
				{
					tile = TILE_HEADQUARTERS;
					headquartersPos = new Pos2(logicX, logicY);
				} else if(nTile == Neighborhood.WALL)
				{
					tile = TILE_WALL;
					
				} else if(nTile > 0)
				{
					if(x != 0 && y != 0)
					{
						//TOOD: naš agentSpoted!
						SpotedAgent sa = new SpotedAgent(nTile, new Pos2(logicX, logicY));
						lstSpotedAgents.add(sa);
						tile = nTile << 8;
					} else
					{
						//To sem jaz!
						tile = TILE_AGENT_ME;
					}
				}
				else
				{
					System.err.println("Uncatched tile!");
				}
				updateLogicTile(logicX, logicY, tile);
			}
	}
	
	//world koordinate so tiste, ki veljajo v svetu našega int Map[][]
	private void updateWorldTile(int x, int y, int tile)
	{
		Map[x][y] = tile;
	}
	
	//logic so tiste koordinate, ki veljajo v svetu agenta
	private void updateLogicTile(int x, int y, int tile)
	{
		updateWorldTile(centerX+x,centerY+y,tile);
	}
	
	public ObservableArea getObservationArea()
	{
		return observableArea;
	}
	
	public boolean isWalkable(Pos2 pos)
	{
		return isWalkable(pos.x, pos.y);
	}
	
	public boolean isWalkable(int x, int y)
	{
		int nTile = getTile(x, y);
		if(nTile != TILE_OUTOFSCOPE)
		{
			if(nTile == TILE_EMPTY)
				return true;
			else
				return false;
		}
		else
			return false;
	}
	
	public int getTile(Pos2 pos) 
	{
		return getTile(pos.x, pos.y);
	}
	
	/**
	 * getTile glede na logične koordinate, to je koordinatni sistem agenta.
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public int getTile(int x, int y) //Glede na izhodišče koordinatnega sistema agenta
	{
		//Mejni pogoji
		if(x < observableArea.minX || x > observableArea.maxX)
			return TILE_OUTOFSCOPE;
		if(y < observableArea.minY || y > observableArea.maxY)
			return TILE_OUTOFSCOPE;
		//vrnemo!
		return Map[centerX + x][centerY+y];
	}
	
	/**
	 * Vpišemo nov Tile, pozicija je glede na koordinatni sistem agenta, ki svet odkriva!
	 * 
	 * @return
	 */
	public void setTile(int x, int y, int nTile)
	{
		//TODO: Mogoče bi lahko kot parametre podal relativno pozicijo glede na headquarters!
		updateLogicTile(x, y, nTile);
	}
	
	public Pos2 getHeadquartersPos()
	{
		return headquartersPos;
	}

	public void setHeadquartersPos(Pos2 headquartersPos)
	{
		this.headquartersPos = headquartersPos;
	}

	public Pos2 getFlagPos()
	{
		return flagPos;
	}

	public void setFlagPos(Pos2 flagPos)
	{
		this.flagPos = flagPos;
	}

	/**
	 * 
	 * @return vrne širina znane mape
	 */
	public int getWidth()	{return observableArea.maxX - observableArea.minX + 1;}
	/**
	 * 
	 * @return vrne višino znane mape
	 */
	public int getHeight()	{return observableArea.maxY - observableArea.minY + 1;}
	
}
